package com.hz.score.search.service;

import com.hz.score.common.dto.PageDto;
import com.hz.score.common.exception.ScoreException;
import com.hz.score.common.util.ConvertUtil;
import com.hz.score.lab.dto.LabInfoDto;
import com.hz.score.lab.service.LabService;
import com.hz.score.search.document.CommData;
import com.hz.score.search.document.EntScore;
import com.hz.score.search.document.IndData;
import com.hz.score.search.document.LabData;
import com.hz.score.search.repository.EntScoreRepository;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Resource
    EntScoreRepository entScoreRepository;

    @Resource
    MongoTemplate mongoTemplate;

    @Resource
    LabService labService;

    /**
     * 按年份在Search模块中新增或更新一条聚合查询数据
     * @param entScore
     */
    public boolean newEntScoreByYear(EntScore entScore) {

        EntScore entScoreOld = entScoreRepository.getEntScoreByUnique(entScore.getCreCod(), entScore.getDataYear());
        if (entScoreOld == null) {
            entScoreRepository.insert(entScore);
            return true;
        } else {
            String[] ignoreArray = {"id", "coId", "creCod", "dataYear", "commDataList", "indDataList", "totalScore", "labDataList"};
            ConvertUtil.convert(entScore, entScoreOld, ignoreArray);
            entScoreRepository.save(entScoreOld);
            return false;
        }
    }

    /**
     * 按年份在Search模块中删除一条聚合查询数据
     * @param creditCode
     * @param dataYear
     */
    public void delEntScore(String creditCode, String dataYear) {
        EntScore entScoreOld = entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
        if (entScoreOld != null) {
            entScoreRepository.delete(entScoreOld);
        }
    }

    /**
     * 按年份在Search模块中获取一条聚合查询数据
     * @param creditCode
     * @param dataYear
     */
    public EntScore getEntScore(String creditCode, String dataYear) {
        return entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
    }

    /**
     * 在Search模块中新增或更新某一企业某一年份经营信息
     * @param creditCode
     * @param dataYear
     * @param commData
     */
    public void newCommData(String creditCode, String dataYear, CommData commData) {
        EntScore entScore = entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
        if (entScore == null) {
            throw new ScoreException("ent score do not exist");
        }
        if (entScore.getCommDataList() == null) {
            entScore.setCommDataList(new ArrayList<>());
        }

        boolean has = false;
        for (CommData commDataIt : entScore.getCommDataList()) {
            if (isSameComm(commDataIt, commData)) {
                commDataIt.setName(commData.getName());
                commDataIt.setVTyp(commData.getVTyp());
                commDataIt.setSca(commData.getSca());
                has = true;
            }
        }

        if (!has) {
            entScore.getCommDataList().add(commData);
        }

        entScoreRepository.save(entScore);
    }

    /**
     * 在Search模块删除某一企业某一年份经营信息
     * @param creditCode
     * @param dataYear
     */
    public void delCommData(String creditCode, String dataYear, String code) {
        EntScore entScore = entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
        if (entScore != null && entScore != null && !CollectionUtils.isEmpty(entScore.getCommDataList())) {
            List<CommData> commDataList = entScore.getCommDataList();
            Iterator<CommData> commDataIt = commDataList.iterator();
            while (commDataIt.hasNext()) {
                CommData commData = commDataIt.next();
                if (isSameComm(commData, code)) {
                    commDataIt.remove();
                }
            }
            entScoreRepository.save(entScore);
        }
    }

    /**
     * 在Search模块中设置某一企业某一年份具体一条经营信息的值
     * @param creditCode
     * @param dataYear
     * @param commDataListParam 经营数据code和经营数据val
     */
    public void setCommDataVal(String creditCode, String dataYear, List<CommData> commDataListParam) {
        EntScore entScore = entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
        if (entScore != null && entScore != null && !CollectionUtils.isEmpty(entScore.getCommDataList())) {
            List<CommData> commDataList = entScore.getCommDataList();
            Iterator<CommData> commDataIt = commDataList.iterator();
            while (commDataIt.hasNext()) {
                CommData commData = commDataIt.next();
                commDataListParam.forEach(commDataParam -> {
                    if (isSameComm(commData, commDataParam.getCode())) {
                        commData.setVal(commDataParam.getVal());
                    }
                });
            }
            entScoreRepository.save(entScore);
        }
    }

    /**
     * 在Search模块中新增或更新某一企业某一年份指标
     * @param creditCode
     * @param dataYear
     * @param indData
     */
    public void newIndData(String creditCode, String dataYear, IndData indData) {
        EntScore entScore = entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
        if (entScore != null) {
            if (entScore.getIndDataList() == null) {
                entScore.setIndDataList(new ArrayList<>());
            }
            boolean has = false;
            for (IndData indDataIt : entScore.getIndDataList()) {
                if (isSameInd(indDataIt, indData)) {
                    indDataIt.setName(indData.getName());
                    indDataIt.setVTyp(indData.getVTyp());
                    indDataIt.setSca(indData.getSca());
                    indDataIt.setParentCode(indData.getParentCode());
                    indDataIt.setLevel(indData.getLevel());
                    has = true;
                }
            }

            if (!has) {
                entScore.getIndDataList().add(indData);
            }
            entScoreRepository.save(entScore);
        }
    }

    /**
     * 在Search模块删除某一企业某一年份指标
     * @param creditCode
     * @param dataYear
     */
    public void delIndData(String creditCode, String dataYear, String code) {
        EntScore entScore = entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
        if (entScore != null && !CollectionUtils.isEmpty(entScore.getIndDataList())) {
            List<IndData> indDataList = entScore.getIndDataList();
            Iterator<IndData> indDataIt = indDataList.iterator();
            while (indDataIt.hasNext()) {
                IndData indData = indDataIt.next();
                if (isSameInd(indData, code)) {
                    indDataIt.remove();
                }
            }
            entScoreRepository.save(entScore);
        }
    }

    /**
     * 在Search模块中设置某一企业某一年份具体一条指标的值
     * @param creditCode
     * @param dataYear
     * @param indDataListParam 指标code和指标val
     */
    public void setIndDataVal(String creditCode, String dataYear, List<IndData> indDataListParam) {
        EntScore entScore = entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
        if (entScore != null && !CollectionUtils.isEmpty(entScore.getIndDataList())) {
            List<IndData> indDataList = entScore.getIndDataList();
            Iterator<IndData> indDataIt = indDataList.iterator();
            while (indDataIt.hasNext()) {
                IndData indData = indDataIt.next();
                indDataListParam.forEach(indDataParam -> {
                    if (isSameInd(indData, indDataParam.getCode())) {
                        indData.setVal(indDataParam.getVal());
                    }
                });
            }
            entScoreRepository.save(entScore);
        }
    }

    /**
     * 在Search模块中设置某一企业某一年份具体一条指标的得分
     * @param creditCode
     * @param dataYear
     * @param indDataListParam 指标code和指标sco
     */
    public void setIndDataSco(String creditCode, String dataYear, List<IndData> indDataListParam) {
        EntScore entScore = entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
        if (entScore != null && !CollectionUtils.isEmpty(entScore.getIndDataList())) {
            List<IndData> indDataList = entScore.getIndDataList();
            Iterator<IndData> indDataIt = indDataList.iterator();
            while (indDataIt.hasNext()) {
                IndData indData = indDataIt.next();
                indDataListParam.forEach(indDataParam -> {
                    if (isSameInd(indData, indDataParam.getCode())) {
                        indData.setSco(indDataParam.getSco());
                    }
                });
            }
            entScoreRepository.save(entScore);
        }
    }

    /**
     * 在Search模块中新增某一企业某一年份标签
     * @param creditCode
     * @param dataYear
     * @param labDataListParam
     */
    public void newLabData(String creditCode, String dataYear, List<LabData> labDataListParam) {
        List<String> codeList = labDataListParam.stream().map(labData -> labData.getCode()).collect(Collectors.toList());
        List<LabInfoDto> labInfoDtoList = labService.getLabEntByCodes(codeList);
        if (labDataListParam.size() != labInfoDtoList.size()) {
            throw new ScoreException("lab do not exist");
        }
        EntScore entScore = entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
        if (entScore != null) {
            if (entScore.getLabDataList() == null) {
                entScore.setLabDataList(new ArrayList<>());
            }
            labDataListParam.forEach(labDataParam -> {
                boolean has = false;
                for (LabData labDataIt : entScore.getLabDataList()) {
                    if (isSameLab(labDataIt, labDataParam)) {
                        has = true;
                    }
                }
                if (!has) {
                    LabInfoDto labInfoDto = labInfoDtoList.stream().filter(lab -> lab.getCode().equals(labDataParam.getCode())).findFirst().get();
                    LabData labData = ConvertUtil.convert(labInfoDto, LabData.class);
                    labData.setValid(true);
                    entScore.getLabDataList().add(labData);
                }
            });
            entScoreRepository.save(entScore);
        }
    }

    /**
     * 在Search模块中失效或起效某一企业某一年份标签
     * @param creditCode
     * @param dataYear
     * @param labDataListParam
     */
    public void setValidLabData(String creditCode, String dataYear, List<LabData> labDataListParam) {
        EntScore entScore = entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
        if (entScore != null && !CollectionUtils.isEmpty(entScore.getLabDataList())) {
            List<LabData> labDataList = entScore.getLabDataList();
            Iterator<LabData> labDataIt = labDataList.iterator();
            while (labDataIt.hasNext()) {
                labDataListParam.forEach(labDataParam -> {
                    LabData labData = labDataIt.next();
                    if (isSameLab(labData, labDataParam)) {
                        labData.setValid(labDataParam.getValid());
                    }
                });
            }
            entScoreRepository.save(entScore);
        }
    }

    /**
     * 在Search模块中失效或起效某一企业某一年份标签
     * @param creditCode
     * @param dataYear
     * @param labDataListParam
     */
    public void delLabData(String creditCode, String dataYear, List<LabData> labDataListParam) {
        EntScore entScore = entScoreRepository.getEntScoreByUnique(creditCode, dataYear);
        if (entScore != null && !CollectionUtils.isEmpty(entScore.getLabDataList())) {
            List<LabData> labDataList = entScore.getLabDataList();
            Iterator<LabData> labDataIt = labDataList.iterator();
            while (labDataIt.hasNext()) {
                labDataListParam.forEach(labDataParam -> {
                    LabData labData = labDataIt.next();
                    if (isSameLab(labData, labDataParam)) {
                        labDataIt.remove();
                    }
                });
            }
            entScoreRepository.save(entScore);
        }
    }

    /**
     * 分页搜索企业年度数据
     *
     * @param entScore
     * @param pageSize
     * @param pageNum
     * @return
     */
    public PageDto<EntScore> pageScoreByYear(EntScore entScore, Integer pageSize, Integer pageNum, String sortInfo) {
        //构建查询条件
        Query query = getQueryByEntScore(entScore);

        //添加分页信息
        long total = mongoTemplate.count(query, EntScore.class);
        query.skip(pageSize * (pageNum - 1));
        query.limit(pageSize);

        //添加排序信息
        setSort(sortInfo, entScore, query);

        List<EntScore> entScoreList = mongoTemplate.find(query, EntScore.class);
        PageDto<EntScore> pageDto = new PageDto<>();
        pageDto.setCurrent(pageNum);
        pageDto.setTotal(total);
        pageDto.setSize(pageSize);
        pageDto.setRecords(entScoreList);
        return pageDto;
    }

    public List<Map> pageScoreByYear(EntScore entScore, String groupInfo) {
        //构建查询条件
        List<Criteria> criteriaList = translateCriteria(entScore);
        List<String> groupFieldList = getGroupField(groupInfo, entScore);
        if (groupFieldList.isEmpty()) {
            throw new ScoreException("group field is empty");
        }
        String[] groupFields = groupFieldList.toArray(new String[groupFieldList.size()]);

        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match((new Criteria()).andOperator(criteriaList)),
                Aggregation.group(groupFields)
                        .count().as("count")
                        .sum("tot_sco").as("sum")
                        .avg("tot_sco").as("avg")
                        .max("tot_sco").as("max")
                        .min("tot_sco").as("min")
        );
        return mongoTemplate.aggregate(aggregation, EntScore.class, Map.class).getMappedResults();
    }

    /**
     * 将entScore查询参数转换成MongoDB查询脚本
     */
    private Query getQueryByEntScore(EntScore entScore) {
        List<Criteria> criteriaList = translateCriteria(entScore);
        Query query = new Query();
        criteriaList.forEach(criteria -> {
            query.addCriteria(criteria);
        });
        return query;
    }

    public List<Criteria> translateCriteria(Object obj) {
        List<Criteria> criteriaList = new ArrayList<>();
        Field[] fields =  obj.getClass().getDeclaredFields();
        List<Criteria> elemMatchList = new ArrayList<>();
        for (Field field : fields) {
            String name = getMongoAnnotation(field);
            Object val = getFieldVal(obj, field);
            if (val == null || val == null) {
                continue;
            }
            Class fieldType = field.getType();
            if (fieldType != List.class) {
                criteriaList.add(translateVal(name, val, fields, obj));
            } else {
                List list = (List) val;
                list.forEach(objIt -> {
                    List<Criteria> criteriaListIt = translateCriteria(objIt);
                    Criteria criteria = Criteria.where(name);
                    if (!CollectionUtils.isEmpty(criteriaListIt)) {
                        elemMatchList.add(criteria.elemMatch(new Criteria().andOperator(criteriaListIt)));
                    }
                });
            }
        }
        if (!CollectionUtils.isEmpty(elemMatchList)) {
            criteriaList.add(new Criteria().andOperator(elemMatchList));
        }
        return criteriaList;
    }

    private Criteria translateVal(String name, Object val, Field[] fields, Object obj) {
        if ((name.equals("val") || name.equals("sco"))) {
            String operation = getOperation(fields, obj);
            if (operation != null) {
                if (operation.equals(">")) {
                    return new Criteria().where(name).gt(val);
                } else if (operation.equals(">=")) {
                    return new Criteria().where(name).gte(val);
                } else if (operation.equals("<")) {
                    return new Criteria().where(name).lt(val);
                } else if (operation.equals("<=")) {
                    return new Criteria().where(name).lte(val);
                } //else if...未完待续
            }
        } else if (name.equals("biz_typ_cod")) {
            String pattern = val + ".*";
            return new Criteria().where(name).regex(pattern);
        }
        return new Criteria().where(name).is(val);
    }

    private String getMongoAnnotation(Field field) {
        org.springframework.data.mongodb.core.mapping.Field fieldMongo;
        fieldMongo = field.getAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);
        if (fieldMongo == null) {
            return null;
        }
        return fieldMongo.value();
    }

    private String getOperation(Field[] fields, Object obj) {
        for(Field field: fields) {
            if (field.getName().equals("operation")) {
                try {
                    field.setAccessible(true);
                    return (String) field.get(obj);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    private Object getFieldVal(Object obj, Field field) {
        org.springframework.data.mongodb.core.mapping.Field fieldMongo;
        fieldMongo = field.getAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);
        if (fieldMongo == null) {
            return null;
        }
        field.setAccessible(true);
        Object val = null;
        try {
            return field.get(obj);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String getMongoAnnotation(String name, Object obj) {
        Field[] fields =  obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (name.equals(field.getName())) {
                org.springframework.data.mongodb.core.mapping.Field fieldMongo;
                fieldMongo = field.getAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);
                if (fieldMongo == null) {
                    return null;
                }
                return fieldMongo.value();
            }
        }
        return null;
    }

    private void setSort(String sortInfo, EntScore entScore, Query query) {
        if (!StringUtils.isEmpty(sortInfo)) {
            String[] sorts = sortInfo.split(",");
            if (sorts.length == 2) {
                String sortField = getMongoAnnotation(sorts[0], entScore);
                if (sortField != null) {
                    if (sorts[1].equals("asc")) {
                        query.with(Sort.by(Sort.Order.asc(sortField)));
                    } else if (sorts[1].equals("desc")) {
                        query.with(Sort.by(Sort.Order.desc(sortField)));
                    }
                }
            }
        }
    }

    private List<String> getGroupField(String groupInfo, EntScore entScore) {
        List<String> groupFieldList = new ArrayList<>();
        if (!StringUtils.isEmpty(groupInfo)) {
            String[] groups = groupInfo.split(",");
            if (groups.length > 0) {
                for (String group : groups) {
                    String groupField = getMongoAnnotation(group, entScore);
                    if (groupField != null) {
                        groupFieldList.add(groupField);
                    }
                }
            }
        }
        return groupFieldList;
    }

    private boolean isSameComm(CommData commData1, CommData commData2) {
        return commData1.getCode().equals(commData2.getCode());
    }

    private boolean isSameComm(CommData commData1, String code) {
        return commData1.getCode().equals(code);
    }

    private boolean isSameInd(IndData indData1, IndData indData2) {
        return indData1.getCode().equals(indData2.getCode());
    }

    private boolean isSameInd(IndData indData1, String code) {
        return indData1.getCode().equals(code);
    }

    private boolean isSameLab(LabData labData1, LabData labData2) {
        return labData1.getCode().equals(labData2.getCode());
    }
}
